#pragma once

#include <iostream>
#include <string>

#include <sys/select.h> //select函数
#include <cerrno>
#include <cstring>
#include "mydll/Sock.hpp"
#include "mydll/log.hpp"


namespace server
{
    using namespace TCP;
    static const int N = sizeof(fd_set)*8; //求出位图fd_set可容纳最大fd数目 开辟对应空间的数组
    static const int defaufd = -1; //初始化数组的fd 作为未使用标记
    class selectserver
    {
        
    public:
        selectserver(int port = 8088)
            :ss(port)
        {}

        void Init()
        {
            ss.Socket();
            ss.Bind();
            ss.Listen();

            for(int i = 0;i<N;++i) fdarray[i] = defaufd; //初始化sockfdfd管理数组
        }


        void Start()
        {
            //本函数中不能直接accept立刻监听 而是靠select监视并给我们反馈 否则会阻塞在accept
            //网络中的新连接被当作读来处理

            fdarray[0] = ss.GetSockFd(); //0下标默认存储服务器sockfd持续监听连接
            while(true)
            {
                //因为rfds是输入输出参数 在一次调用后会被select修改里面的值返回给我们 所以我们要让select一直监听必须每次都修改
                //其次，maxfd每次也是在动态变化的，也需要动态更新

                fd_set rfds;
                FD_ZERO(&rfds); //初始化全部位图为0
                int maxfd = -1; //获取最大的fd值


                //更新描述符到rfds中
                for(int i = 0;i<N;++i) 
                {
                    if(fdarray[i] != defaufd) 
                    {
                        FD_SET(fdarray[i],&rfds);
                        if(fdarray[i] > maxfd) maxfd = fdarray[i];
                    }
                }
                int n = select(maxfd+1,&rfds,nullptr,nullptr,nullptr); //阻塞式

                switch(n)
                {
                    case 0: //等待超时
                    {
                        logMessage(Debug, "timeout, %d: %s", errno, strerror(errno));
                        break;
                    }
                    case -1: //等待失败
                    {
                        logMessage(Warning, "%d: %s", errno, strerror(errno));
                        break;
                    }
                    default: //n>0表示有n个文件描述符可读取 但没有accept 在全连接队列中等待我们连接
                    {
                        logMessage(Debug, "有就绪事件发生了|连接数: %d", n);
                        HandlerEvent(rfds);
                        DebugPrint();
                        break;
                    }
                }
                sleep(1);
            }

        }
        void Accepter() //获取连接
        {
            std::cout << "有一个新连接到来了" << std::endl;
            //这里在进行Accept不会被阻塞
            std::string client_ip;
            uint16_t client_port;
            int client_fd = ss.Accept(&client_ip,&client_port);
            if(client_fd < 0) return; //获取失败则停止执行

            int pos = 1; //从1开始 0下标默认服务器fd
            //查找sockfd空位插入
            for(pos;pos<N;++pos) { if(fdarray[pos] == defaufd) break; }
            if(pos >= N) //文件描述符数组已满
            {
                close(client_fd);
                logMessage(Warning, "sockfd array[] full");
            }
            else fdarray[pos] = client_fd;
        }

        void Recver(int fdpos) //信息读取函数 参数为fd下标
        {
            char buf[1024] = {0}; //这里存在报文完整性无法保证的BUG
            ssize_t n = recv(fdarray[fdpos],buf,sizeof(buf),0); //这里因为有读取请求则一定不会阻塞
            if(n > 0)
            {
                if(buf[n-1] == '\n') buf[n-1] = '\0';
                else buf[n] = '\0';

                std::cout<<"client: "<<buf<<std::endl;

                std::string echo = "[server]:";
                echo += buf;

                send(fdarray[fdpos],echo.c_str(),echo.size(),0); //发送回去
            }
            else
            {
                if(n == 0) //对方断开连接
                    logMessage(Info,"Client quit... fd:%d",fdarray[fdpos]);
                else
                    logMessage(Warning,"recv error! Client fd:%d",fdarray[fdpos]);

                close(fdarray[fdpos]); //关闭连接
                fdarray[fdpos] = defaufd; //该fd位置置为空状态
            }
        }

        void HandlerEvent(fd_set &rfds)
        {
            for(int i = 0;i<N;++i)
            {
                if(fdarray[i] == defaufd) continue; //如果是空fd则不予执行后面的判断
                //检查服务器套接字中位图是否可读 可连接accept 如果是服务器fd且在rfds中有连接 则进行连接处理
                if(fdarray[i] == ss.GetSockFd() && FD_ISSET(ss.GetSockFd(), &rfds)) 
                {
                    Accepter(); //这里因为有连接条件 所以一定不会被阻塞
                }
                else if(FD_ISSET(fdarray[i], &rfds)) //除了服务器fd，如果普通文件描述符有请求，则是有数据需要服务器读取
                {
                    //这里理论上可以调用ServiceIO函数
                    Recver(i);
                }
            }

        }
        void DebugPrint()
        {
            std::cout<<"fdarray: ";
            for(int i = 0;i<N;++i) if(fdarray[i] != defaufd) std::cout<<fdarray[i]<<" ";
            std::cout<<"\n";
        }

        ~selectserver() { ss.Close_Sockfd(); }
    private:
        SockServer ss;
        
        //v2
        int fdarray[N]; //sockfd管理数组
    };
}